package com.gusto.engine.clusterant.algorithms.antsclustering.ants;

import org.apache.log4j.Logger;

import com.gusto.engine.clusterant.algorithms.antsclustering.Plan;
import com.gusto.engine.clusterant.model.Point;

/**
 * <p>Generic Behavior of an artificial Ant</p> 
 * 
 * @author amokrane.belloui@gmail.com
 */
public abstract class Ant {
	
	private Logger log = Logger.getLogger(getClass());
	
	protected Plan plan;
	protected int row;
	protected int col;
	protected int perception = 2;
	protected Object object = null;
	protected Long maxMoves; // INIT
	protected int moves = -1;
	protected Integer maxMovesWithObject = null; // INIT
	protected Integer movesWithObject = 0;
	protected boolean finishedJob = false;
	
	public Ant(Plan plan, Long maxMoves, Integer maxMovesWithObject) {
		// Initialize Parameters
		this.plan = plan;
		this.maxMoves = maxMoves;
		this.maxMovesWithObject = maxMovesWithObject;
		// Put the Ant somewhere
		this.moveSomehere();
	}
	
	public String toString() {
		return "Ant " + row + "-" + col + " " + (hasPickedObject() ? "(object : " + object + ")" : "(no object)");
	}
	
	public int getRow() {
		return row;
	}

	public int getCol() {
		return col;
	}
	 
	public int getPerception() {
		return perception;
	}

	public void setPerception(int perception) {
		this.perception = perception;
	}
	
	public Object getObject() {
		return object;
	}
	
	public Long getMaxMoves() {
		return maxMoves;
	}

	public Integer getMoves() {
		return moves;
	}
	
	/**
	 * Pick an object
	 */
	public synchronized void pick() {
		this.object = plan.getObject(this.row, this.col);
		log.debug("Picked " + object + " in " + row + " " + col + " at move #" + moves);
		boolean ok = plan.removeObjectIfNotEmpty(this.row, this.col);
		if (!ok) {
			this.object = null;
		}
	}
	
	/**
	 * Drop the current object held
	 */
	public synchronized void drop() {
		Point objectPut = plan.putObjectIfEmpty(this.row, this.col, this.object);
		log.debug("Dropped " + object + " in " + row + " " + col +  " at move #" + moves);
		if (objectPut != null) {
			this.object = null;
			movesWithObject = 0;
		}
	}
	
	/**
	 * Return true if the ant has picked an object
	 * @return
	 */
	public boolean hasPickedObject() {
		return object != null;
	}
	
	/**
	 * Move to a position in the plan
	 * @param row
	 * @param col
	 */
	public void moveTo(int row, int col) {
		if (!this.finishedJob) {
			this.row = row;
			this.col = col;
			
			// Generic movement behavior
			moves++;
			if (hasPickedObject()) {
				movesWithObject++;
			}
			// Too many moves (must drop)
			if (this.mustDrop()) {
				_doForcedDrop(false);
			}
			if (moves >= maxMoves) {
				finishJob();
			}
		}
	}
	
	/**
	 * Move somewhere (calls move to override the behavior)
	 * Adds generic behavior : increments the number of moves, drops the objects if the ant has finished it's job
	 */
	public final void moveSomehere() {
		if (!this.finishedJob) {
			// Specific Implementation of the movement of the Ant
			
			this.move();

			// Generic movement behavior
			moves++;
			if (hasPickedObject()) {
				movesWithObject++;
			}
			// Too many moves (must drop)
			if (this.mustDrop()) {
				_doForcedDrop(false);
			}
			if (moves >= maxMoves) {
				finishJob();
			}
		}
	}
	
	/**
	 * Finishes the Ant job by dropping the object it has picked
	 */
	public void finishJob() {
		if (! this.finishedJob) {
			_doForcedDrop(true);
			this.finishedJob = true;
			onFinishedJob();
		}
	}
	
	protected void onFinishedJob() {
		// Nothing by default
	}
	
	/**
	 * Choose the next destination of the Ant, and set the coordinates
	 */
	protected abstract void move();
	
	/**
	 * Template method, to get the ant to do an action : <br/>
	 *  * A pick -> doPick
	 *  * A drop -> doDrop
	 *  Depending of the state of the ant and the cell of the plan where the Ant is.
	 */
	public final synchronized void doSomething() {
		if (!this.finishedJob) {
			if (!hasPickedObject() && !plan.isEmpty(this.row, this.col)) {
				// Delegate the behavior of Picking (Pick or not)
				doPick();
			}
			else if (hasPickedObject() && plan.isEmpty(this.row, this.col)) {
				// Delegate the Behavior of dropping (Drop or not)
				doDrop();
			}
		}
	}
	
	/**
	 * Behavior of picking an object
	 * @param objectOnPlan
	 * @param dist
	 */
	protected abstract void doPick();
	
	/**
	 * Behavior of dropping an object
	 * @param dist
	 */
	protected abstract void doDrop();
	
	/**
	 * Indicates if the Ant should drop the object exceeded maxMoves with object 
	 * @return
	 */
	public boolean mustDrop() {
		if (maxMovesWithObject != null) {
			return movesWithObject >= maxMovesWithObject;
		}
		return false;
	}
	
	/**
	 * Method that can be overridden to implement special forced drop behavior 
	 */
	protected void doForcedDrop(boolean endJob) {
		
	}
	
	protected void _doForcedDrop(boolean endJob) {
		if (endJob) {
			log.debug("Job-end forced drop");
		}
		if (hasPickedObject()) {
			// Override
			doForcedDrop(endJob);
			
			// Move until you find an empty cell
			while (!plan.isEmpty(this.row, this.col)) {
				move();
			}
			// When you found an empty cell, drop the object
			if (plan.isEmpty(this.row, this.col)) {
				log.debug("Forced Drop of " + this.object + " in " + this.row + "-" + this.col);
				this.drop();
			}  
			this.move();
		}
	}
	
	/**
	 * Return true if the Ant has finished its job
	 * @return
	 */
	public boolean isFinishedJob() {
		return this.finishedJob;
	}
	
}
